#include "finder.h"

#include <QLabel>
#include <QGridLayout>
#include <QProgressBar>
#include <QSpinBox>
#include <QStyle>
#include <QToolButton>

FinderThread::FinderThread( QObject * parent )
    : QThread( parent ) {}

FinderThread::~FinderThread() {
    mutex.lock();
    abort = true;
    condition.wakeOne();
    mutex.unlock();

    wait();
}

void FinderThread::findOptimum( const State data, int aCheck, int aSleep ) {
    QMutexLocker lock( &mutex );
    // store data for run()
    state = data;
    checkPeriod = aCheck;
    sleepPeriod = aSleep;
    // abort & wakeup or start thread
    if ( isRunning() )
    {
        restart = true;
        stop = true;
        condition.wakeOne();
    } else
    { start( QThread::LowestPriority ); }
}

void FinderThread::stopSearch() {
    QMutexLocker lock( &mutex );
    stop = true;
}

void FinderThread::run() {
    while ( true )
    {
        // copy data to stack variables
        mutex.lock();
        State origin = state;
        origin.setStep( 1 );
        const int tSleep = sleepPeriod;
        const int tCheck = checkPeriod;
        mutex.unlock();

        OptimumValues theBest;
        std::chrono::minutes optimum{ std::chrono::minutes::max() };
        std::chrono::minutes tmp;

        const auto optimumCheck = [&, this]( const State & data ) {
            tmp = State( data ).completeEvent( std::chrono::hours{ tSleep }, std::chrono::hours{ tCheck } );
            if ( tmp < optimum )
            {
                optimum = tmp;
                theBest.powerWeight = data.getType( ProductType::Power ).weight;
                theBest.powerFactor = data.getType( ProductType::Power ).factor;
                theBest.assetWeight = data.getType( ProductType::Asset ).weight;
                theBest.assetFactor = data.getType( ProductType::Asset ).factor;
                theBest.xpWeight = data.getType( ProductType::XP ).weight;
                theBest.reduction = data.getType( ProductType::XP ).factor;
                emit optimumFound( static_cast<int>( optimum.count() ), theBest );
            }
        };

        int stepPower = 10, minPower = 10, maxPower = 30, totalPower = 100;
        int stepXp = 8, minXP = 4, maxXP = 100, totalXP = 100;
        int stepAsset = 8, minAsset = 4, maxAsset = 100, totalAsset = 100;
        int stepCost = 8, minCost = 4, maxCost = 100, totalCost = 100;
        if ( origin.getAutoControl() )
        {
            // first try current settings
            optimumCheck( origin );

            /**
             * @note
             * - power: brute force factor, weight calc from factor
             * - xp: brute force factor as cost reduction(if level < 75 ), brute force weight
             * - asset: brute force factor, weight calc from priority ratio
             */
            int currentProgress = 0;
            int totalProgress = ( maxPower - minPower ) / stepPower * ( maxXP - minXP ) / stepXp * ( maxAsset - minAsset ) / stepAsset;
            for ( int powerFactor = minPower; powerFactor < maxPower && !stop; powerFactor += stepPower )
            {
                origin.setProductFactor( ProductType::Power, static_cast<double>( powerFactor ) / totalPower );

                for ( int xpWeight = minXP; xpWeight <= maxXP && !stop; xpWeight += stepXp )
                {
                    origin.setProductWeight( ProductType::XP, static_cast<double>( xpWeight ) / totalXP );

                    for ( int assetFactor = minAsset; assetFactor <= maxAsset && !stop; assetFactor += stepAsset )
                    {
                        origin.setProductFactor( ProductType::Asset, static_cast<double>( assetFactor ) / totalAsset );

                        if ( abort )
                        { return; }

                        if ( origin.getType( UpgradeType::Cost_Reduction_Assets ).getLevel() < 75 )
                        {
                            for ( int costFactor = minCost; costFactor <= maxCost; costFactor += stepCost )
                            {
                                origin.setProductFactor( ProductType::XP, static_cast<double>( costFactor ) / totalCost );
                                optimumCheck( origin );
                            }
                        } else
                        { optimumCheck( origin ); }

                        emit progressChanged( ++currentProgress, totalProgress );
                    }
                }
            }
        } else
        { optimumCheck( origin ); }

        emit progressChanged( 1, 1 );

        //  sleep or go again
        mutex.lock();
        if ( !restart )
        { condition.wait( &mutex ); }
        restart = false;
        stop = false;
        mutex.unlock();
    }
}

Finder::Finder( std::shared_ptr<State> aState, QWidget * parent )
    : QWidget( parent )
    , playerData( aState ) {
    QGridLayout * layout = new QGridLayout{ this };
    checkBox = new QSpinBox{ this };
    sleepBox = new QSpinBox{ this };
    modeButton = new QToolButton{ this };
    searchButton = new QToolButton{ this };
    cancelButton = new QToolButton{ this };
    okButton = new QToolButton{ this };
    bar = new QProgressBar{ this };
    text = new QLabel{ this };

    layout->addWidget( checkBox, 0, 0, 1, 2 );
    layout->addWidget( sleepBox, 1, 0, 1, 2 );
    layout->addWidget( modeButton, 2, 0 );
    layout->addWidget( searchButton, 2, 1 );
    layout->addWidget( bar, 0, 0 );
    layout->addWidget( text, 1, 0, 1, 2 );
    layout->addWidget( cancelButton, 0, 1 );
    layout->addWidget( okButton, 0, 1 );
    // mode button
    modeButton->setToolTip( "Find optimal values" );
    modeButton->setObjectName( "ModeButton" );
    modeButton->setCheckable( true );
    modeButton->setSizePolicy( QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Fixed );
    // search button
    searchButton->setSizePolicy( QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Fixed );
    searchButton->setText( "Complete event" );
    searchButton->setToolTip( "Do magic and complete event!" );
    connect( searchButton, &QToolButton::released, this, &Finder::searchStart );
    // check every N hours
    checkBox->setSizePolicy( QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Fixed );
    checkBox->setPrefix( "Spend all every " );
    checkBox->setMaximum( 23 );
    checkBox->setValue( 6 );
    checkBox->setMinimum( 1 );
    checkBox->setSuffix( " h." );
    // sleep N hours
    sleepBox->setSizePolicy( QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Fixed );
    sleepBox->setPrefix( "Sleep for at least " );
    sleepBox->setMaximum( 23 );
    sleepBox->setValue( 8 );
    sleepBox->setMinimum( 1 );
    sleepBox->setSuffix( " h." );

    // cancel button
    cancelButton->setIcon( style()->standardIcon( QStyle::SP_MediaStop ) );
    cancelButton->setToolTip( "Cancel search" );
    cancelButton->hide();
    connect( cancelButton, &QToolButton::released, this, &Finder::searchCancel );
    // progress bar
    bar->setSizePolicy( QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Fixed );
    bar->hide();
    connect( &thread, &FinderThread::progressChanged, this, &Finder::searchProgress, Qt::QueuedConnection );

    // result
    text->setSizePolicy( QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Fixed );
    text->setTextFormat( Qt::RichText );
    text->setWordWrap( true );
    text->setAlignment( Qt::AlignCenter );
    text->hide();
    connect( &thread, &FinderThread::optimumFound, this, &Finder::findOptimum );
    //  ok button
    okButton->setIcon( style()->standardIcon( QStyle::SP_DialogOkButton ) );
    okButton->hide();
    connect( okButton, &QToolButton::released, this, &Finder::searchFinish );
}

void Finder::updateData() {
    modeButton->setChecked( playerData->getAutoControl() );
    modeButton->setIcon( QIcon::fromTheme( modeButton->isChecked() ? "lock" : "unlock" ) );
}

void Finder::searchStart() {
    modeButton->hide();
    searchButton->hide();
    sleepBox->hide();
    checkBox->hide();

    if ( playerData->getTotalProduction( true ) == 0 )
    {
        bar->show();
        bar->setValue( bar->maximum() );
        okButton->show();
        text->setText( QString( "<b>This madness will never end<br>(c) porkbob</b>" ) );
        text->show();
    } else
    {
        searchButton->setEnabled( false );
        bar->show();
        bar->reset();
        bar->setValue( 0 );
        cancelButton->show();
        text->setText( "<b>Hold my beer...</b>" );
        text->show();

        thread.findOptimum( *playerData, checkBox->value(), sleepBox->value() );
    }
    isSearching = true;
}

void Finder::searchCancel() {
    text->hide();
    cancelButton->hide();
    bar->hide();

    modeButton->show();
    checkBox->show();
    sleepBox->show();
    searchButton->show();

    thread.stopSearch();
    isSearching = false;
}

void Finder::searchFinish() {
    text->hide();
    okButton->hide();
    bar->hide();

    modeButton->show();
    checkBox->show();
    sleepBox->show();
    searchButton->show();
    isSearching = false;
}

void Finder::searchProgress( int current, int total ) {
    bar->setValue( current * bar->maximum() / total );
    if ( total == current )
    {
        searchButton->setEnabled( true );
        cancelButton->hide();
        okButton->setVisible( isSearching );
    }
}

void Finder::findOptimum( int minutes, const OptimumValues & weight ) {
    text->setText( QString( "<b>The event will end in:<br>~ %1 days, %2 hours</b>" ).arg( minutes / 1440 ).arg( ( minutes % 1440 ) / 60 ) );
    emit optimumFound( weight );
}
